package GUI;
import helpers.CustomLogger;

import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.event.*;
import Model.IObserver;
import Model.Task;
import Model.ITaskManager;
import Model.TaskTreeIterator;

import java.awt.event.*;
import java.util.*;
import java.text.*;
import java.util.HashSet;


public class TaskManagerGui 
	extends JFrame 
	implements ActionListener
			  ,TableModelListener
			  ,IObserver
	
	{

	private ArrayList<HashSet<Task>> mLevels = new ArrayList<HashSet<Task>>();
	private ArrayList<Task>  		 mAllTasks = new ArrayList<Task>();
	//private HashSet<String> mParentTaskNames = new HashSet<String>();
	private final String ADD_SINGLE		= "Add-Single";
	private final String ADD_PERIODIC 	= "Add-Periodic";
	private final String ADD_COMPLEX 	= "Add-Complex";
	private final String DELETE_TASK 	= "Delete-Task";
	private final String SHOW_RESOLVED 	= "Show-Resolved";
	private final String HIDE_RESOLVED 	= "Hide-Resolved";
	private final String RESOLVE 		= "Resolve-Task";
	private final String REOPEN 		= "Reopen-Task";
	private final String UNDO 			= "Undo";
	private final String REDO  			= "Redo";
	
	private boolean 		mIsDebug = false;
	private ITaskManager 	mModel;
	private static final long serialVersionUID = 1L;
	private JTextField 		mSingleTaskName;
	private JTextField 		mPeriodicTaskName;
	private JTextField 		mComplexTaskName;
	private JTextField 		mDeadline;
	private JComboBox 		mInterval;
	private JButton 		mAddSingleTask;
	private JButton 		mAddPeriodicTask;
	private JButton 		mAddComplexTask;
	private JButton 		mDelete;
	private JButton 		mShowResolvedButton;
	private JButton 		mHideResolvedButton;
	private JButton 		mUndo;
	private JButton 		mRedo;
	private JButton 		mResolve;
	private boolean 		mShowResolved = true;
	private JButton 		mReopen;
	private int 			mFieldHeightDefault = 30;
	private JTable 			mTable;
	private Font 			mFontDefault = new Font("Tahoma", Font.PLAIN, 16);
	//private ArrayList<Task> mTaskList; 
	
	public TaskManagerGui(ITaskManager iModel)
	{
		// Main window		
		super("Neuromancer Task Manager");
		
		if(!mIsDebug
			&& null == iModel)
		{
			CustomLogger.getInst().log("TaskManager Gui Error: Model is null !");
			JOptionPane.showMessageDialog(this, "TaskManager Gui Error: Model is null ! ");
			System.exit(1);
		}
		mModel = iModel;
		mModel.registerObserver(this);
		frameInit();
		getContentPane().setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(675, 780);
		initComponents();
		setResizable(false);
		setVisible(true);

		CustomLogger.getInst().log("Created Main GUI Window.");
	}

	
	/**
	 * Creates components and initializes their properties
	 */
	private void initComponents()
	{
		// INITIALIZE THE GUI COMPONENTS
		// =============================
	
		// THE TABLE
		update();
		
		// INITIALIZE THE TOP ROW 
		// ======================
		JPanel wTopRowButtons =  new JPanel();
		wTopRowButtons.setLayout(new GridLayout(1, 4, 10, 0));
		wTopRowButtons.setPreferredSize(new Dimension(630,40));
		mShowResolvedButton = new JButton("Show Resolved");
		mShowResolvedButton.setFont(mFontDefault);
		mShowResolvedButton.addActionListener(this);
		mShowResolvedButton.setActionCommand(SHOW_RESOLVED);
		wTopRowButtons.add(mShowResolvedButton);
		
		mHideResolvedButton = new JButton("Hide Resolved");
		mHideResolvedButton.setFont(mFontDefault);
		mHideResolvedButton.addActionListener(this);
		mHideResolvedButton.setActionCommand(HIDE_RESOLVED);
		wTopRowButtons.add(mHideResolvedButton);
		
		mUndo = new JButton("Undo");
		mUndo.setFont(mFontDefault);
		mUndo.addActionListener(this);
		mUndo.setActionCommand(UNDO);
		wTopRowButtons.add(mUndo);
		
		mRedo = new JButton("Redo");
		mRedo.setFont(mFontDefault);
		mRedo.addActionListener(this);
		mRedo.setActionCommand(REDO);
		wTopRowButtons.add(mRedo);
	
		
		getContentPane().add(wTopRowButtons);
		

		mTable.getTableHeader().setFont(mFontDefault);
		mTable.setFont(mFontDefault);
		mTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		mTable.getColumnModel().getColumn(0).setPreferredWidth(50);
		mTable.getColumnModel().getColumn(1).setPreferredWidth(300);
		mTable.getColumnModel().getColumn(2).setPreferredWidth(250);
		mTable.getModel().addTableModelListener(this);
		mTable.setRowHeight(20);
		mTable.setSize(600, 400);
		
		// Add a scroll bar the the table
		JScrollPane wScrollPane = new JScrollPane(mTable);
		wScrollPane.setPreferredSize(new Dimension(630,350));
		mTable.setFillsViewportHeight(true);
		getContentPane().add(wScrollPane);		
		
		
		// UNDER-TABLE ROW
		// ===============
		JPanel wUnderTableRow = new JPanel();
		wUnderTableRow.setLayout(new GridLayout(1, 3, 15, 0));
		
		// The Delete button
		mDelete = new JButton("Delete");
		mDelete.setSize(50, mFieldHeightDefault);
		mDelete.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mDelete.setFont(mFontDefault);
		mDelete.addActionListener(this);
		mDelete.setActionCommand(DELETE_TASK);
		wUnderTableRow.add(mDelete);
		
		// The Resolve Button
		mResolve = new JButton("Resolve");
		mResolve.setSize(50, mFieldHeightDefault);
		mResolve.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mResolve.setFont(mFontDefault);
		mResolve.addActionListener(this);
		mResolve.setActionCommand(RESOLVE);
		wUnderTableRow.add(mResolve);
		
		// The Reopen Button
		mReopen = new JButton("Reopen");
		mReopen.setSize(50, mFieldHeightDefault);
		mReopen.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mReopen.setFont(mFontDefault);
		mReopen.addActionListener(this);
		mReopen.setActionCommand(REOPEN);
		wUnderTableRow.add(mReopen);
		getContentPane().add(wUnderTableRow);
		
		
		// SINGLE-TASK ROW
		// ===============
		JPanel wSingleTaskRow = new JPanel();
		wSingleTaskRow.setLayout(new GridLayout(1, 3, 15, 0));
		wSingleTaskRow.setPreferredSize(new Dimension(630,50));
	
		// Task Name TextField 
		JPanel wSingleTaskNameBox = new JPanel();
		wSingleTaskNameBox.setLayout(new BoxLayout(wSingleTaskNameBox, BoxLayout.Y_AXIS));
		JLabel wSingleTaskNameLabel = new JLabel("Name of Single Task");
		mSingleTaskName = new JTextField("Test Single Task");
		mSingleTaskName.setSize(100, mFieldHeightDefault);
		mSingleTaskName.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mSingleTaskName.setFont(mFontDefault);
		wSingleTaskNameBox.add(wSingleTaskNameLabel);
		wSingleTaskNameBox.add(mSingleTaskName);
		wSingleTaskRow.add(wSingleTaskNameBox);
		
		// Task Deadline TextField 
		JPanel wSingleTaskDeadlineBox = new JPanel();
		wSingleTaskDeadlineBox.setLayout(new BoxLayout(wSingleTaskDeadlineBox, BoxLayout.Y_AXIS));
		JLabel wSingleTaskDeadlineLabel = new JLabel("Due Date (YYYY-MM-DD)");
		mDeadline = new JTextField("2011-11-11");
		mDeadline.setSize(50, mFieldHeightDefault);
		mDeadline.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mDeadline.setFont(mFontDefault);
		wSingleTaskDeadlineBox.add(wSingleTaskDeadlineLabel);
		wSingleTaskDeadlineBox.add(mDeadline);
		wSingleTaskRow.add(wSingleTaskDeadlineBox);
		
		// The Add Single button
		mAddSingleTask = new JButton("Add Single");
		mAddSingleTask.setSize(50, mFieldHeightDefault);
		mAddSingleTask.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mAddSingleTask.setFont(mFontDefault);
		mAddSingleTask.addActionListener(this);
		mAddSingleTask.setActionCommand(ADD_SINGLE);
		wSingleTaskRow.add(mAddSingleTask);
		getContentPane().add(wSingleTaskRow);
		
		
		// PERIODIC-TASK ROW
		// ===============
		JPanel wPeriodicTaskRow = new JPanel();
		wPeriodicTaskRow.setLayout(new GridLayout(1, 3, 15, 0));
		wPeriodicTaskRow.setPreferredSize(new Dimension(630,50));
	
		// Task Name TextField 
		JPanel wPeriodicTaskNameBox = new JPanel();
		wPeriodicTaskNameBox.setLayout(new BoxLayout(wPeriodicTaskNameBox, BoxLayout.Y_AXIS));
		JLabel wPeriodicTaskNameLabel = new JLabel("Name of Periodic Task");
		mPeriodicTaskName = new JTextField("Test Periodic Task");
		mPeriodicTaskName.setSize(100, mFieldHeightDefault);
		mPeriodicTaskName.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mPeriodicTaskName.setFont(mFontDefault);
		wPeriodicTaskNameBox.add(wPeriodicTaskNameLabel);
		wPeriodicTaskNameBox.add(mPeriodicTaskName);
		wPeriodicTaskRow.add(wPeriodicTaskNameBox);
		
		JPanel wPeriodicTaskIntervalBox = new JPanel();
		wPeriodicTaskIntervalBox.setLayout(new BoxLayout(wPeriodicTaskIntervalBox, BoxLayout.Y_AXIS));
		JLabel wPeriodicIntervalLabel = new JLabel("Interval");

		String[] wTaskTypes = { "Daily", "Weekly", "Monthly" };
		mInterval = new JComboBox(wTaskTypes);
		mInterval.setSelectedIndex(1);
		mInterval.setFont(mFontDefault);
	
		wPeriodicTaskIntervalBox.add(wPeriodicIntervalLabel);
		wPeriodicTaskIntervalBox.add(mInterval);
		wPeriodicTaskRow.add(wPeriodicTaskIntervalBox);
		
		// The Add Periodic button
		mAddPeriodicTask = new JButton("Add Periodic");
		mAddPeriodicTask.setSize(50, mFieldHeightDefault);
		mAddPeriodicTask.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mAddPeriodicTask.setFont(mFontDefault);
		mAddPeriodicTask.addActionListener(this);
		mAddPeriodicTask.setActionCommand(ADD_PERIODIC);
		wPeriodicTaskRow.add(mAddPeriodicTask);
		getContentPane().add(wPeriodicTaskRow);
		
		
		// COMPLEX-TASK ROW
		// ================
		JPanel wComplexTaskRow = new JPanel();
		wComplexTaskRow.setLayout(new GridLayout(1, 3, 15, 0));
		wComplexTaskRow.setPreferredSize(new Dimension(630,50));
	
		// Task Name TextField 
		JPanel wComplexTaskNameBox = new JPanel();
		wComplexTaskNameBox.setLayout(new BoxLayout(wComplexTaskNameBox, BoxLayout.Y_AXIS));
		JLabel wComplexTaskNameLabel = new JLabel("Name of Complex Task");
		mComplexTaskName = new JTextField("Test Complex Task");
		mComplexTaskName.setSize(100, mFieldHeightDefault);
		mComplexTaskName.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mComplexTaskName.setFont(mFontDefault);
		wComplexTaskNameBox.add(wComplexTaskNameLabel);
		wComplexTaskNameBox.add(mComplexTaskName);
		wComplexTaskRow.add(wComplexTaskNameBox);
		
		// Placeholder
		JPanel wComplexPlaceholder = new JPanel();
		wComplexPlaceholder.setLayout(new BoxLayout(wComplexPlaceholder, BoxLayout.Y_AXIS));
		wComplexTaskRow.add(wComplexPlaceholder);
		
		// The Add Complex button
		mAddComplexTask = new JButton("Add Complex");
		mAddComplexTask.setSize(50, mFieldHeightDefault);
		mAddComplexTask.setPreferredSize(new Dimension(200,mFieldHeightDefault+2));
		mAddComplexTask.setFont(mFontDefault);
		mAddComplexTask.addActionListener(this);
	    mAddComplexTask.setActionCommand(ADD_COMPLEX);
		wComplexTaskRow.add(mAddComplexTask);
		getContentPane().add(wComplexTaskRow);
		
		CustomLogger.getInst().log("Initialized GUI");
		
		//testPanel.add(mAddSingleTask);		
	}
	
	/**
	 * The call-back method called by the object observed
	 * There is no logging here because it will clutter the
	 * log file.	
	 */
	public void update()
	{
		// Do a deep recursive copy of the Task tree list
		//mTaskList = new ArrayList<Task>(10);
		//mParentTaskNames.clear();
		mLevels.clear();
		figureOutNestings(mModel.getTasks(), mLevels);
		mAllTasks.clear();
		
		for( Task wIter : mModel.getTasks())
		{
			
			mAllTasks.add(wIter);
			if(wIter.getSubtasks().size() > 0)
			{
				TaskTreeIterator wIter2 = (TaskTreeIterator)wIter.iterator();
				//Skip the first one -- already added
				// Now the real loop
				while (wIter2.hasNext())
				{
					wIter2.next();
					mAllTasks.add(wIter2.getCurrent());
					
				}
			}
			
		}
		/*
		// Iterate through all the parent tasks
		for (int i = 0; i < mModel.getTasks().size(); ++i)
		{
			// Add the parent task itself first
			mTaskList.add(mModel.getTasks().get(i));
			mParentTaskNames.add(mModel.getTasks().get(i).getName());
			// And also add all its sub-tasks
			for(int j = 0; j < mModel.getTasks().get(i).getSubtasks().size(); ++j)
			{
				mTaskList.add(mModel.getTasks().get(i).getSubtasks().get(j));
			}
		}
		*/
		
		// REFRESH GUI
		// ===========
		DefaultTableModel wModel = null;
		if(mIsDebug)
		{
			String[] wColumnNames = {"Status","Task Description","Due"};
		    Object[][] wData = {{true, "It's time to", "kick donkey"}, {false, "And chew bubble", "gum"}, {false, "And I'm all out of", "gum"}};
		    wModel = new DefaultTableModel(wData, wColumnNames);
		}
		else
		{
			
			// Count the actual number of tasks to be added to the table.
			// This will depend on three things:
			// 	1. The actual number of tasks in the repository (duh) and
			//  2. Whether or not we want to display resolved tasks and
			//  3. How many tasks in the repository have been resolved
			int wNumberOfTasksToDisplay = 0;
			for (int i = 0; i < mAllTasks.size(); ++i)
			{
				if(!(mAllTasks.get(i).getDone() && !mShowResolved))
				{
					++wNumberOfTasksToDisplay;
				}		
			}
			String[] wColumnNames = {"Status","Task Description","Due"};
		    Object[][] wData = new Object[wNumberOfTasksToDisplay][3];
		    
		    // The "j" counter will be incremented only when a task is really added to the destination array.
		    // (destination array counter index)
			for (int i = 0, j = 0; i < mAllTasks.size(); ++i)
			{
				if(!(mAllTasks.get(i).getDone() && !mShowResolved))
				{
					wData[j][0] = mAllTasks.get(i).getDone();
					// Figure out how deep the task is in the tree so that we could offset its name
					int wDepth = 0;
					String wSpaces = "";
					for(int k = 0; k < mLevels.size(); ++k)
					{
						if(mLevels.get(k).contains(mAllTasks.get(i)))
						{
							wDepth = k;
							break;
						}
					}
					for (int k = 0; k < wDepth; ++k)
					{
						wSpaces += "  ";
					}					
					wData[j][1] = wSpaces + mAllTasks.get(i).getName();
					
					wData[j][2] = mAllTasks.get(i).getDateString();
					++j;
				}		
			}
			wModel = new DefaultTableModel(wData, wColumnNames);
		}
		if(null == mTable)
		{
			mTable = new JTable(wModel) 
			{

	            private static final long serialVersionUID = 1L;

	            /*@Override
	            public Class getColumnClass(int column) {
	            return getValueAt(0, column).getClass();
	            }*/
	            @Override
	            public Class<?> getColumnClass(int column) {
	                switch (column) {
	                    case 0:
	                        return Boolean.class;
	                    case 1:
	                        return String.class;
	                    case 2:
	                        return String.class;
	                    default:
	                        return String.class;
	                }
	            }
	        };
		}
		else
		{
			mTable.setModel(wModel);
			mTable.getColumnModel().getColumn(0).setPreferredWidth(50);
			mTable.getColumnModel().getColumn(1).setPreferredWidth(300);
			mTable.getColumnModel().getColumn(2).setPreferredWidth(250);
			mTable.getModel().addTableModelListener(this);
		}
		mTable.getTableHeader().setReorderingAllowed(false);

        
	}
  // ======================
	// = CONTROLLER SECTION =
	// ======================
	@Override
	public void actionPerformed(ActionEvent e) 
	{
		
		String wAction;
		wAction = e.getActionCommand().toString();
		
		// ADD SINGLE TASK
		// ===============
		if(wAction.equals(ADD_SINGLE))
		{
			 if(containsTask(mModel.getTasks(),mSingleTaskName.getText().trim()))
			 {
				 CustomLogger.getInst().log("[WARNING] Tried to add a duplicate task.");
				 JOptionPane.showMessageDialog(this, "A task with such name already exists. Please be original.");
				 return;
			 }
			 try 
			 {  
				 String wDateString = mDeadline.getText().trim();
				 DateFormat wFormatter ; 
				 Date wDate ; 
				 wFormatter = new SimpleDateFormat("yyyy-MM-dd");
				 wDate = (Date)wFormatter.parse(wDateString); 	
				 //JOptionPane.showMessageDialog(this, "Added task: " + mTaskName.getText());
				 
				Integer wRow = mTable.getSelectedRow();
				if(-1 == wRow)
				{
					// Create a simple new task
					mModel.addTask(mSingleTaskName.getText().trim(), wDate,"Single","Single");
					CustomLogger.getInst().log("Created a single task : " + mSingleTaskName.getText().trim());
				}
				else
				{
					// Create a sub-task
					try
					{
						String wParentName = mTable.getValueAt(wRow, 1).toString().trim();
						mModel.addSubTask(wParentName, mSingleTaskName.getText().trim(),"Single","Single",wDate);
						CustomLogger.getInst().log("Created a sub task : " + mSingleTaskName.getText().trim());
					}
					catch (Exception x)
					{
						JOptionPane.showMessageDialog(this, x.getMessage());
						mTable.clearSelection();
						
					}
				}
			 } 
			 catch (ParseException ex)
			 {
				 JOptionPane.showMessageDialog(this, ex.getMessage());
				
			 }
		}
		// ADD PERIODIC TASK
		// =================
		if(wAction.equals(ADD_PERIODIC))
		{
			 if(containsTask(mModel.getTasks(),mPeriodicTaskName.getText().trim()))
			 {
				 CustomLogger.getInst().log("[WARNING] Tried to add a duplicate task.");
				 JOptionPane.showMessageDialog(this, "A task with such name already exists. Please be original.");
				 return;
			 }
			
			Integer wRow = mTable.getSelectedRow();
			if(-1 == wRow)
			{
				// Create a simple new task
				CustomLogger.getInst().log("Created a Periodic task : " + mPeriodicTaskName.getText().trim());
				mModel.addTask(mPeriodicTaskName.getText().trim(), new Date(), "Periodic",  mInterval.getSelectedItem().toString());
			}
			else
			{
				// Create a sub-task
				try
				{
					String wParentName = mTable.getValueAt(wRow, 1).toString().trim();
					mModel.addSubTask(wParentName,mPeriodicTaskName.getText().trim(), "Periodic" , mInterval.getSelectedItem().toString(),null);
				}
				catch (Exception x)
				{
					JOptionPane.showMessageDialog(this, x.getMessage());
					mTable.clearSelection();	
					
				}
			}
	
			 	 
		}	
		// ADD COMPLEX TASK
		// ================
		if(wAction.equals(ADD_COMPLEX))
		{
			 if(containsTask(mModel.getTasks(),mComplexTaskName.getText().trim()))
			 {
				 CustomLogger.getInst().log("[WARNING] Tried to add a duplicate task.");
				 JOptionPane.showMessageDialog(this, "A task with such name already exists. Please be original.");
				 return;
			 }
			 
			Integer wRow = mTable.getSelectedRow();
			if(-1 == wRow)
			{
				// Create a simple new task
				mModel.addTask(mComplexTaskName.getText().trim(), new Date(), "Complex", "Complex");
				CustomLogger.getInst().log("Added Complex task : " + mComplexTaskName.getText().trim());
			}
			else
			{
				// Create a sub-task
				try
				{
					String wParentName = mTable.getValueAt(wRow, 1).toString().trim();
					mModel.addSubTask(wParentName, mComplexTaskName.getText().trim(),  "Complex", "Complex",null);
					CustomLogger.getInst().log("Added sub task :" + mComplexTaskName.getText().trim());
				}
				catch (Exception x)
				{
					JOptionPane.showMessageDialog(this, x.getMessage());
					mTable.clearSelection();	
				}
			}
			
		}	
		// DELETE TASK
		// ===========
		else if (wAction.equals(DELETE_TASK))
		{
			Integer wTaskToDeleteRow = mTable.getSelectedRow();
			if(-1 == wTaskToDeleteRow)
			{
				CustomLogger.getInst().log("Tried to delete non existend task!");
				JOptionPane.showMessageDialog(this, "No task selected to delete.");
			}
			else
			{
				String wName = mTable.getValueAt(wTaskToDeleteRow, 1).toString().trim();
				CustomLogger.getInst().log("Deleted task : " + wName);
				//JOptionPane.showMessageDialog(this, "Deleted task: " + wName);
				try
				{
					mModel.deleteTask(wName);
				}
				
				catch(Exception deleteTaskExceptionCatcher)
				{
					
				}
			}
		}
		// SHOW THE RESOLVED TASKS IN THE TABLE
		// ====================================
		else if (wAction.equals(SHOW_RESOLVED))
		{
			mShowResolved = true;
			update();
		}
		// HIDE THE RESOLVED TASKS IN THE TABLE
		// ====================================
		else if (wAction.equals(HIDE_RESOLVED))
		{
			mShowResolved = false;
			update();
		}
		// RESOLVE A TASK
		// ==============
		else if (wAction.equals(RESOLVE))
		{
	    	// RESOLVE TASK
	    	// ============
			Integer wRow = mTable.getSelectedRow();
			if(-1 == wRow)
			{
				CustomLogger.getInst().log("Tried to resolve a non existant task");
				JOptionPane.showMessageDialog(this, "No task selected to resolve.");
			}
			else
			{
				String wName = mTable.getValueAt(wRow, 1).toString().trim();
				CustomLogger.getInst().log("Resolved task : " + wName);
				//JOptionPane.showMessageDialog(this, "Resolved task: " + wName);
				mModel.setTaskStatus(true, wName);
			}

		}
		// REOPEN A TASK
		// =============
		else if (wAction.equals(REOPEN))
		{
			Integer wRow = mTable.getSelectedRow();
			if(-1 == wRow)
			{
				JOptionPane.showMessageDialog(this, "No task selected to reopen.");
				CustomLogger.getInst().log("Tried to reopen a non existant task");
			}
			else
			{
				String wName = mTable.getValueAt(wRow, 1).toString().trim();
				//JOptionPane.showMessageDialog(this, "Resolved task: " + wName);
				mModel.setTaskStatus(false, wName);
				CustomLogger.getInst().log("Reopened Task : " + wName);
			}
		}
		// UNDO LAST COMMAND
		// =================
		else if (wAction.equals(UNDO))
		{
			try
			{
				CustomLogger.getInst().log("Undoing action ...");
				mModel.undo();
			}
			catch (EmptyStackException ex)
			{
				JOptionPane.showMessageDialog(this, "No operation to undo.");
				CustomLogger.getInst().log("Nothing to undo");
			}
			catch (Exception ex)
			{
				JOptionPane.showMessageDialog(this, "An exception have been thrown; we don't know what it is, but at least we've been prepared :P");
				CustomLogger.getInst().log("Unresolved exception : " + ex.getMessage());
			}
		}
		// REDO LAST COMMAND
		// =================
		else if (wAction.equals(REDO))
		{
			try
			{
				CustomLogger.getInst().log("Redoing action");
				mModel.redo();
			}
			catch (EmptyStackException ex)
			{
				CustomLogger.getInst().log("No operation to redo");
				JOptionPane.showMessageDialog(this, "No operation to redo.");
			}
			catch (Exception ex)
			{
				JOptionPane.showMessageDialog(this, "An exception have been thrown; we don't know what it is, but at least we've been prepared :P");
				CustomLogger.getInst().log("Unresolved Exception : " + ex.getMessage());
			}
		}
		
	}

    public void tableChanged(TableModelEvent e) 
    {
    	// RESOLVE / UNRESOLVE TASK
    	// ========================
    	try
    	{
	    	int wRow = e.getFirstRow();
	        int wColumn = e.getColumn();
	        if(0 == wColumn
	        	&& 0 <= wRow)
	        {
	        	String wName = mTable.getValueAt(wRow, 1).toString().trim();
	        	Boolean wResolved = (Boolean)mTable.getValueAt(wRow, 0);
	        	mModel.setTaskStatus(wResolved, wName);
	        	mTable.clearSelection();	
	        }
    	}
    	catch (Exception ex)
    	{
    		JOptionPane.showMessageDialog(this, "Don't do that (c) Terminator III." + ex.getMessage());
    	}

    	
    }
    
    /***
     * Checks if the task tree contains a specified task name
     * @param iTaskTree
     * @param iName
     * @return
     */
    private boolean containsTask(ArrayList<Task> iTaskTree, String iName)
    {
    	if(null == iTaskTree 
    	|| null == iName
    	|| iName.length() == 0)
    	{
    		return false;
    	}
    	for(Task i : iTaskTree)
    	{
    		// Check the task itself for matching
    		if(i.getName().equals(iName))
    		{
    			return true;
    		}
    		
    		// Check all its children for matching
    		if(null != i.getSubtasks()
    			&& i.getSubtasks().size() > 0)
    		{
    			for(Task j : i.getSubtasks())
    			{
    				if(j.getName().equals(iName))
    				{
    					return true;
    				}
    			}
    		}
    	}
    	
    	return false;
    }

    
    private void figureOutNestings(ArrayList<Task> iParentTasks, ArrayList<HashSet<Task>> oLevels)
    {
    	// Recursive, but unusually so!
    	HashSet<Task> wHash = new HashSet<Task> ();
    	ArrayList<Task> wNextLevelTasks = new ArrayList<Task>();
    	for (int i = 0; i < iParentTasks.size(); ++i)
    	{
    		wHash.add(iParentTasks.get(i));
    		for(int j = 0; j < iParentTasks.get(i).getSubtasks().size(); ++j)
    		{
    			wNextLevelTasks.add(iParentTasks.get(i).getSubtasks().get(j));
    		}
    	}
    	oLevels.add(wHash);
    	if(wNextLevelTasks.size() > 0)
    	{
    		// recursive call
    		figureOutNestings(wNextLevelTasks,oLevels);
    	}
    	
    }
}



